package com.llt.user.service.user.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.llt.common.core.constants.CacheConstants;
import com.llt.common.core.constants.Constants;
import com.llt.common.core.constants.HttpConstants;
import com.llt.common.core.domain.LoginUser;
import com.llt.common.core.domain.LoginUserVO;
import com.llt.common.core.domain.R;
import com.llt.common.core.enums.ResultCode;
import com.llt.common.core.enums.UserIdentity;
import com.llt.common.core.enums.UserStatus;
import com.llt.common.core.utils.ThreadLocalUtil;
import com.llt.common.message.service.AliSmsService;
import com.llt.common.redis.service.RedisService;
import com.llt.common.security.exception.ServiceException;
import com.llt.common.security.service.TokenService;

import com.llt.user.domain.user.vo.UserVO;
import com.llt.user.domain.user.dto.UserDTO;
import com.llt.user.domain.user.dto.UserUpdateDTO;
import com.llt.user.domain.user.po.User;
import com.llt.user.manager.UserCacheManager;
import com.llt.user.mapper.user.UserMapper;
import com.llt.user.service.user.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author llt
 * @since 2024-10-23
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private AliSmsService aliSmsService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private UserCacheManager userCacheManager;

    @Value("${sms.code-expiration:5}")
    private Long phoneCodeExpiration;

    @Value("${sms.send-limit:5}")
    private Integer sendLimit;

    @Value("${jwt.secret}")
    private String secret;

    // 开关打开-true
    @Value("${sms.is-send:false}")
    private boolean isSend;

    @Value("${file.oss.downloadUrl}")
    private String downloadUrl;

    @Override
    public boolean sendCode(UserDTO dto) {
        // 1. 判断手机号是否合法
        if (!checkPhone(dto.getPhone())) {
            throw new ServiceException(ResultCode.FAILED_USER_PHONE);
        }

        // 2. 防止用户频繁获取验证码(一分钟内只能一次)
        String phoneCodeKey = getPhoneCodeKey(dto.getPhone());
        // 2.1 从redis中获取key的剩余时间并进行比较
        Long expireTime = redisService.getExpire(phoneCodeKey, TimeUnit.SECONDS);
        if (expireTime != null && (phoneCodeExpiration * 60 - expireTime) < 60) {
            throw new ServiceException(ResultCode.FAILED_FREQUENT);
        }

        // 3. 每天获取次数限制50次, 第二天清零(方便测试这里写了3)
        String sendCodeTimeKey = getCodeTimeKey(dto.getPhone());
        Long sendTimes = redisService.getCacheObject(sendCodeTimeKey, Long.class);
        if (sendTimes != null && sendTimes >= sendLimit) {
            throw new ServiceException(ResultCode.FAILED_TIME_LIMIT);
        }

        // 4. 生成随机验证码
        String code = isSend ? RandomUtil.randomNumbers(6) : Constants.DEFAULT_CODE;

        // 5. 存储验证码
        redisService.setCacheObject(phoneCodeKey, code, phoneCodeExpiration, TimeUnit.MINUTES);

        // 6. 发送验证码给指定用户
        if (isSend) {
            boolean sendMobileCode = aliSmsService.sendMobileCode(dto.getPhone(), code);
            if (!sendMobileCode) {
                throw new ServiceException(ResultCode.FAILED_SEND_CODE);
            }
        }

        // 7. 访问次数加一
        redisService.increment(sendCodeTimeKey);
        if (sendTimes == null) {
            // 证明是当天第一次
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            redisService.expire(sendCodeTimeKey, seconds, TimeUnit.SECONDS);
        }

        return true;
    }

    @Override
    public String codeLogin(String phone, String code) {
        // 1. 比对验证码
        checkCode(phone, code);

        // 2. 判断新/老用户
        User user = this.lambdaQuery()
                .eq(User::getPhone, phone)
                .one();

        // 3. 新用户 --- 注册
        if (user == null) {
            user = new User();
            user.setPhone(phone);
            user.setStatus(UserStatus.Normal.getValue());
            userMapper.insert(user);
        }

        // 4. 生成 token 返回
        return tokenService.createToken(user.getUserId(), secret, UserIdentity.ORDINARY.getValue(), user.getNickName(), user.getHeadImage());
    }

    @Override
    public boolean logout(String token) {
        // 1. 判断 token 是否有前缀
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }

        // 2. 从 redis 中获取到用户信息
        // 2.1 先从令牌中拿到 userKey, 再拼接成 key, 最后从 redis 中删
        boolean delete = tokenService.delete(token, secret);

        return delete;
    }

    @Override
    public R<LoginUserVO> info(String token) {
        // 1. 判断 token 是否有前缀
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }

        // 2. 从 redis 中获取到用户信息
        // 2.1 先从令牌中拿到 userKey, 再拼接成 key, 最后从 redis 中取
        LoginUser loginUser = tokenService.getLoginUser(token, secret);
        if (loginUser == null) {
            return R.fail();
        }

        // 3. 封装 VO 返回
        LoginUserVO vo = new LoginUserVO();
        vo.setUserName(loginUser.getUserName());
        if (StrUtil.isNotEmpty(loginUser.getHeadImage())) {
            vo.setHeadImage(downloadUrl + loginUser.getHeadImage());
        }
        return R.ok(vo);
    }

    @Override
    public UserVO detail() {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        UserVO userVO = userCacheManager.getUserById(userId);
        if (userVO == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        if (StrUtil.isNotEmpty(userVO.getHeadImage())) {
            userVO.setHeadImage(downloadUrl + userVO.getHeadImage());
        }
        return userVO;
    }

    @Override
    public int edit(UserUpdateDTO userUpdateDTO) {
        // 1. 获取当前登陆用户
        User user = getLoginUser();
        user.setNickName(userUpdateDTO.getNickName());
        user.setSex(userUpdateDTO.getSex());
        user.setSchoolName(userUpdateDTO.getSchoolName());
        user.setMajorName(userUpdateDTO.getMajorName());
        user.setPhone(userUpdateDTO.getPhone());
        user.setEmail(userUpdateDTO.getEmail());
        user.setWechat(userUpdateDTO.getWechat());
        user.setIntroduce(userUpdateDTO.getIntroduce());

        // 2. 更新用户缓存
        updatrUserCache(user);

        return userMapper.updateById(user);
    }

    @Override
    public int updateHeadImage(String headImage) {
        // 1. 获取当前登陆用户
        User user = getLoginUser();
        user.setHeadImage(headImage);

        // 2. 更新用户缓存
        updatrUserCache(user);

        return userMapper.updateById(user);
    }

    private void updatrUserCache(User user) {
        userCacheManager.refreshUser(user);
        tokenService.refreshLoginUser(user.getNickName(), user.getHeadImage(), ThreadLocalUtil.getUserKey());
    }

    private User getLoginUser() {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        return user;
    }

    private void checkCode(String phone, String code) {
        // 1. 从 redis 中拿到存的验证码进行比对
        String phoneCodeKey = getPhoneCodeKey(phone);
        String cacheCode = redisService.getCacheObject(phoneCodeKey, String.class);
        if (StrUtil.isEmpty(cacheCode)) {
            throw new ServiceException(ResultCode.FAILED_CODE_INVALID);
        }
        if (!cacheCode.equals(code)) {
            throw new ServiceException(ResultCode.FAILED_CODE_ERROR);
        }
        // 2. 验证码比对成功, 删除 redis 中的验证码
        redisService.deleteObject(phoneCodeKey);
    }

    public static boolean checkPhone(String phone) {
        Pattern regex = Pattern.compile("^1[2|3|4|5|6|7|8|9][0-9]\\d{8}$");
        Matcher m = regex.matcher(phone);
        return m.matches();
    }

    private String getPhoneCodeKey(String phone) {
        return CacheConstants.PHONE_CODE_KEY + phone;
    }

    private String getCodeTimeKey(String phone) {
        return CacheConstants.CODE_TIME_KEY + phone;
    }
}
